﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GoodTrader.GTWave
{
    public class GTWaveManager
    {
        // 트렌드 라인 목록
        List<GTTrendLine> m_TrendLineList = new List<GTTrendLine>();
        public List<GTTrendLine> TrendLineList
        {
            get { return m_TrendLineList; }
            set { m_TrendLineList = value; }
        }
        List<GTWavePoint> mTrendLinePoint = new List<GTWavePoint>();

        List<GTWavePoint> mUpDownPointList = new List<GTWavePoint>();
        public List<GTWavePoint> UpDownPointList
        {
            get { return mUpDownPointList; }
            set { mUpDownPointList = value; }
        }
        List<GTWavePoint> mWaveList = new List<GTWavePoint>();
        public List<GTWavePoint> WaveList
        {
            get { return mWaveList; }
            set { mWaveList = value; }
        }

        private int m_TrendRange = 120;
        /**
         * @brief 이 함수는 파동의 고점 저점 목록을 가지고 트렌트 라인을 찾아 냅니다.
         * @remark : 트렌드 라인은 전체 목록에서 제일 큰 추세선을 하나 찾고 이웃한 두 점에서 각도가 큰 추세선을 하나더 찾습니다.
         *           이러한 행위를 모든 점에 대하여 수행합니다. 단 마지막 점에 대해서는 의미가 없으므로 수행하지 않습니다.
         */
        public void FindTrendLine()
        {
            m_TrendLineList.Clear();
            // 마지막 4개는 의미가 없으므로 만들지 않는다.
            // 4를 빼는 이유는 상승파와 하락파가 각각 2개씩 있기 때문이다.
            for(int i = 1; i < mWaveList.Count - 4; i++)
            {
                GTWavePoint wavePt = mWaveList[i];

                GTTrendLine newTrend = new GTTrendLine();

                GTWavePoint curPt = mWaveList[i];
                GTWavePoint nearPt = mWaveList[i + 2];
                GTWavePoint farPt = mWaveList[i + 4];

                int foundIndex = FindPeakPoint(mWaveList, i);
                double deltaSmallY = 0.0;
                int deltaSmallX = 0;
                double deltaBigY = 0.0;
                int deltaBigX = 0;

                deltaSmallX = Math.Abs(curPt.X - nearPt.X);
                deltaSmallY = Math.Abs(curPt.Y - nearPt.Y) * 200;
                deltaBigX = Math.Abs(curPt.X - (curPt.X + m_TrendRange));
                deltaBigY = (deltaSmallY * deltaBigX) / deltaSmallX;
                deltaBigY = deltaBigY / 200.0;

                newTrend.SP.X = curPt.X;
                newTrend.SP.Y = curPt.Y;
                newTrend.Near.X = curPt.X + m_TrendRange;

                if ((nearPt.Y - curPt.Y) > 0.0)
                    newTrend.Near.Y = curPt.Y + deltaBigY;
                else
                    newTrend.Near.Y = curPt.Y - deltaBigY;

                newTrend.Far.X = curPt.X + m_TrendRange;
                deltaSmallX = Math.Abs(curPt.X - farPt.X);
                deltaSmallY = Math.Abs(curPt.Y - farPt.Y) * 200;
                deltaBigX = Math.Abs(curPt.X - (curPt.X + m_TrendRange));

                deltaBigY = (deltaSmallY * deltaBigX) / deltaSmallX;
                deltaBigY = deltaBigY / 200.0;

                if ((farPt.Y - curPt.Y) > 0.0)
                    newTrend.Far.Y = curPt.Y + deltaBigY;
                else
                    newTrend.Far.Y = curPt.Y - deltaBigY;

                GTWavePoint muchFarPt = mWaveList[foundIndex];

                newTrend.MuchFar.X = curPt.X + m_TrendRange + 200;
                deltaSmallX = Math.Abs(curPt.X - muchFarPt.X);
                deltaSmallY = Math.Abs(curPt.Y - muchFarPt.Y) * 200;
                deltaBigX = Math.Abs(curPt.X - (curPt.X + m_TrendRange + 200));

                deltaBigY = (deltaSmallY * deltaBigX) / deltaSmallX;
                deltaBigY = deltaBigY / 200.0;

                if ((muchFarPt.Y - curPt.Y) > 0.0)
                    newTrend.MuchFar.Y = curPt.Y + deltaBigY;
                else
                    newTrend.MuchFar.Y = curPt.Y - deltaBigY;

                m_TrendLineList.Add(newTrend);
            }
        }

        /**
         * @brief 
         * @param aWaveList : 파동 접점 목록
         * @param aSIndex : 시작 인덱스
         * @return : 찾은 인덱스를 반환한다.
         * @remark
         */ 
        public int FindPeakPoint(List<GTWavePoint> aWaveList, int aSIndex)
        {
            int found = aSIndex;
            if (aWaveList.Count < 1)
                return found;
            GTWavePoint sPt = aWaveList[aSIndex];
            GTWavePoint ePt = aWaveList[aSIndex + 2];

            double maxAngle = Angle(sPt.X, sPt.Y, ePt.X, ePt.Y);
            // 시작 점부터 찾아 본다.
            for (int i = aSIndex + 4; i < aWaveList.Count; i = i + 2)
            {
                ePt = aWaveList[i];
                double curAngle = Angle(sPt.X, sPt.Y,ePt.X, ePt.Y);
                if (curAngle > maxAngle)
                {
                    found = i;
                    maxAngle = curAngle;
                }
            }

            return found;
        }

        const double Rad2Deg = 180.0 / Math.PI;
        const double Deg2Rad = Math.PI / 180.0;

        /// <summary>
        /// Calculates angle in radians between two points and x-axis.
        /// </summary>
        private double Angle(double a_SX, double a_SY, double a_EX, double a_EY)
        {
            double angle = Math.Atan2(a_EY - a_SY, a_EX - a_SX) * Rad2Deg;
            if (angle < 0.0)
                angle += 360.0;
            return angle;
        }

        /**
         * @brief 이 함수는 주어진 데이터 소스를 활용하여 고점을 저점을 찾아 낸다.
         * @param aSrc : 소스 데이터
         */ 
        public void MakeUpDowPointList(double[] aSrc)
        {
            // 값 목록이 2보다 작을 때는 아무일도 하지 않는다.
            if (aSrc.Length < 2)
                return;
            // 먼저 출력 목록을 비워준다.
            mUpDownPointList.Clear();

            double firstPoint = aSrc[0];
            double lastPoint = firstPoint;

            GTWavePoint newPoint = new GTWavePoint();
            newPoint.X = 0;
            newPoint.Y = firstPoint;
            newPoint.WaveDir = GTWaveDir.WAVE_EVEN;

            mUpDownPointList.Add(newPoint);

            GTWaveDir oldSlope = GTWaveDir.WAVE_EVEN;
            GTWaveDir curSlope = GTWaveDir.WAVE_EVEN;

            for (int i = 1; i < aSrc.Length; i++)
            {
                firstPoint = aSrc[i - 1];
                lastPoint = aSrc[i];
                double incValue = lastPoint - firstPoint;
                if (incValue > 0)
                {
                    curSlope = GTWaveDir.WAVE_UP;
                }
                else if (incValue < 0)
                {
                    curSlope = GTWaveDir.WAVE_DOWN;
                }
                else
                {
                    curSlope = oldSlope;
                }

                if (oldSlope != GTWaveDir.WAVE_EVEN && oldSlope != curSlope)
                {
                    newPoint = new GTWavePoint();
                    // -1 을 해 주는 이유는 기울기가 바뀐것은 그 다음 점에 확인할 수 있기 때문에
                    // 기울기가 바뀌기 시작한 것은 그 전 점이기 때문이다.
                    newPoint.X = i - 1;
                    newPoint.Y = firstPoint;
                    if (curSlope == GTWaveDir.WAVE_UP)
                        newPoint.WaveDir = GTWaveDir.WAVE_UP;
                    else if (curSlope == GTWaveDir.WAVE_DOWN)
                        newPoint.WaveDir = GTWaveDir.WAVE_DOWN;
                    else
                        newPoint.WaveDir = GTWaveDir.WAVE_EVEN;
                    mUpDownPointList.Add(newPoint);
                }
                oldSlope = curSlope;
            }

            newPoint = new GTWavePoint();
            newPoint.X = aSrc.Length - 1;
            newPoint.Y = aSrc[aSrc.Length - 1];
            if (curSlope == GTWaveDir.WAVE_DOWN)
                newPoint.WaveDir = GTWaveDir.WAVE_UP;
            else if (curSlope == GTWaveDir.WAVE_UP)
                newPoint.WaveDir = GTWaveDir.WAVE_DOWN;
            else
                newPoint.WaveDir = GTWaveDir.WAVE_EVEN;

            mUpDownPointList.Add(newPoint);
        }

        private int ExPoint = 2;
        /**
         * @brief 이 함수는 이동평균으로 찾아낸 점을 기초로 실제 파동의 점을 찾아 낸다. 
         * @param aHighSrc : 높은 값들에 대한 데이터 소스
         * @param aLowSrc : 낮은 값들에 대한 데이터 소스
         */
        public void FindRealWavePoint(double[] aHighSrc, double[] aLowSrc)
        {
            if (aHighSrc.Length < 1 || aLowSrc.Length < 1)
                return;
            mWaveList.Clear();

            GTWavePoint newPoint = new GTWavePoint();
            newPoint.X = 0;
            newPoint.Y = aHighSrc[0];
            newPoint.WaveDir = GTWaveDir.WAVE_EVEN;
            mWaveList.Add(newPoint);

            // 발견된 점에서 첫번째 점과 마지막 점은 제외 시킨다.
            // 왜냐하면 시작점은 파동이 시작되기 전이고
            // 끝점은 파동이 끝나지 않은 점이기 때문에 신뢰성이 보장되지 않기 때문이다.
            // 대신 실제 데이터를 기반으로 시작점과 끝점을 넣어준다.
            int minIndex = 0;
            int maxIndex = 0;
            double MinVal = 0.0;
            double MaxVal = 0.0;
            int count = mUpDownPointList.Count;
            for (int i = 1; i < count; i++)
            {
                GTWavePoint curPt = mUpDownPointList[i];
                int startIndex = mUpDownPointList[i - 1].X;
                int lastIndex = mUpDownPointList[i].X;
                // 파동의 방향에 따라 상승 시작 점이었으면 저점을
                // 하락의 시작점이었으면 고점을 찾아서 넣어 준다.
                // 조사 지점은 바로 전 점의 X좌표까지 이다.
                if (curPt.WaveDir == GTWaveDir.WAVE_UP)
                {
                    // 상승하는 포인트이므로 구간내에서 가장 작은 값을 찾아 온다.
                    // - ExPoint를 해주는 것은 가끔 최고점이나 최저점이 발견된 점보다 1 ~ 2 봉 뒤에 있기 때문이다.
                    FindMinMax(startIndex - ExPoint, lastIndex, aLowSrc, ref minIndex, ref maxIndex, ref MinVal, ref MaxVal);
                    newPoint = new GTWavePoint();
                    newPoint.X = minIndex;
                    newPoint.Y = MinVal;
                    newPoint.WaveDir = GTWaveDir.WAVE_UP;
                    mWaveList.Add(newPoint);
                }
                else if (curPt.WaveDir == GTWaveDir.WAVE_DOWN)
                {
                    // 하락하는 포인트이므로 구간내에서 가장 큰 값을 찾아 온다.
                    // - ExPoint를 해주는 것은 가끔 최고점이나 최저점이 발견된 점보다 1 ~ 2 봉 뒤에 있기 때문이다.
                    FindMinMax(startIndex - ExPoint, lastIndex, aHighSrc, ref minIndex, ref maxIndex, ref MinVal, ref MaxVal);
                    newPoint = new GTWavePoint();
                    newPoint.X = maxIndex;
                    newPoint.Y = MaxVal;
                    newPoint.WaveDir = GTWaveDir.WAVE_DOWN;
                    mWaveList.Add(newPoint);
                }
            }

            newPoint.X = aHighSrc.Length - 1;
            newPoint.Y = aHighSrc[aHighSrc.Length - 1];
            newPoint.WaveDir = GTWaveDir.WAVE_EVEN;
            mWaveList.Add(newPoint);
        }


        /**
         * @brief 이 함수는 이동평균으로 찾아낸 점을 기초로 실제 파동의 점을 찾아 낸다. 
         * @param aHighSrc : 높은 값들에 대한 데이터 소스
         * @param aLowSrc : 낮은 값들에 대한 데이터 소스
         */
        public void FindRealWavePoint(double[] aHighSrc, double[] aLowSrc, double[] a_DateSrc)
        {
            if (aHighSrc.Length < 1 || aLowSrc.Length < 1)
                return;
            mWaveList.Clear();

            GTWavePoint newPoint = new GTWavePoint();
            newPoint.X = 0;
            newPoint.Y = aHighSrc[0];
            newPoint.WaveDate = (int)a_DateSrc[0];
            newPoint.WaveDir = GTWaveDir.WAVE_EVEN;
            mWaveList.Add(newPoint);

            // 발견된 점에서 첫번째 점과 마지막 점은 제외 시킨다.
            // 왜냐하면 시작점은 파동이 시작되기 전이고
            // 끝점은 파동이 끝나지 않은 점이기 때문에 신뢰성이 보장되지 않기 때문이다.
            // 대신 실제 데이터를 기반으로 시작점과 끝점을 넣어준다.
            int minIndex = 0;
            int maxIndex = 0;
            double MinVal = 0.0;
            double MaxVal = 0.0;
            int count = mUpDownPointList.Count;
            for (int i = 1; i < count; i++)
            {
                GTWavePoint curPt = mUpDownPointList[i];
                int startIndex = mUpDownPointList[i - 1].X;
                int lastIndex = mUpDownPointList[i].X;
                // 파동의 방향에 따라 상승 시작 점이었으면 저점을
                // 하락의 시작점이었으면 고점을 찾아서 넣어 준다.
                // 조사 지점은 바로 전 점의 X좌표까지 이다.
                if (curPt.WaveDir == GTWaveDir.WAVE_UP)
                {
                    // 상승하는 포인트이므로 구간내에서 가장 작은 값을 찾아 온다.
                    // - ExPoint를 해주는 것은 가끔 최고점이나 최저점이 발견된 점보다 1 ~ 2 봉 뒤에 있기 때문이다.
                    FindMinMax(startIndex - ExPoint, lastIndex, aLowSrc, ref minIndex, ref maxIndex, ref MinVal, ref MaxVal);
                    newPoint = new GTWavePoint();
                    newPoint.X = minIndex;
                    newPoint.WaveDate = (int)a_DateSrc[minIndex];
                    newPoint.Y = MinVal;
                    newPoint.WaveDir = GTWaveDir.WAVE_UP;
                    mWaveList.Add(newPoint);
                }
                else if (curPt.WaveDir == GTWaveDir.WAVE_DOWN)
                {
                    // 하락하는 포인트이므로 구간내에서 가장 큰 값을 찾아 온다.
                    // - ExPoint를 해주는 것은 가끔 최고점이나 최저점이 발견된 점보다 1 ~ 2 봉 뒤에 있기 때문이다.
                    FindMinMax(startIndex - ExPoint, lastIndex, aHighSrc, ref minIndex, ref maxIndex, ref MinVal, ref MaxVal);
                    newPoint = new GTWavePoint();
                    newPoint.X = maxIndex;
                    newPoint.WaveDate = (int)a_DateSrc[maxIndex];
                    newPoint.Y = MaxVal;
                    newPoint.WaveDir = GTWaveDir.WAVE_DOWN;
                    mWaveList.Add(newPoint);
                }
            }

            newPoint.X = aHighSrc.Length - 1;
            newPoint.WaveDate = (int)a_DateSrc[aHighSrc.Length - 1];
            newPoint.Y = aHighSrc[aHighSrc.Length - 1];
            newPoint.WaveDir = GTWaveDir.WAVE_EVEN;
            mWaveList.Add(newPoint);
        }

        /**
         * @brief 이 함수는 이동평균으로 찾아낸 점을 기초로 실제 파동의 점을 찾아 낸다. 
         * @param aSrc : 데이터 소스
         */ 
        public void FindRealWavePoint(double[] aSrc)
        {
            if (aSrc.Length < 1)
                return;
            mWaveList.Clear();

            GTWavePoint newPoint = new GTWavePoint();
            newPoint.X = 0;
            newPoint.Y = aSrc[0];
            newPoint.WaveDir = GTWaveDir.WAVE_EVEN;
            mWaveList.Add(newPoint);

            // 발견된 점에서 첫번째 점과 마지막 점은 제외 시킨다.
            // 왜냐하면 시작점은 파동이 시작되기 전이고
            // 끝점은 파동이 끝나지 않은 점이기 때문에 신뢰성이 보장되지 않기 때문이다.
            // 대신 실제 데이터를 기반으로 시작점과 끝점을 넣어준다.
            int minIndex = 0;
            int maxIndex = 0;
            double MinVal = 0.0;
            double MaxVal = 0.0;
            int count = mUpDownPointList.Count;
            for(int i = 1; i < count; i++)
            {
                GTWavePoint curPt = mUpDownPointList[i];
                int startIndex = mUpDownPointList[i - 1].X;
                int lastIndex = mUpDownPointList[i].X;
                // 파동의 방향에 따라 상승 시작 점이었으면 저점을
                // 하락의 시작점이었으면 고점을 찾아서 넣어 준다.
                // 조사 지점은 바로 전 점의 X좌표까지 이다.
                if (curPt.WaveDir == GTWaveDir.WAVE_UP)
                {
                    // 상승하는 포인트이므로 구간내에서 가장 작은 값을 찾아 온다.
                    FindMinMax(startIndex, lastIndex, aSrc, ref minIndex, ref maxIndex, ref MinVal, ref MaxVal);
                    newPoint = new GTWavePoint();
                    newPoint.X = minIndex;
                    newPoint.Y = MinVal;
                    newPoint.WaveDir = GTWaveDir.WAVE_UP;
                    mWaveList.Add(newPoint);
                }
                else if (curPt.WaveDir == GTWaveDir.WAVE_DOWN)
                {
                    // 하락하는 포인트이므로 구간내에서 가장 큰 값을 찾아 온다.
                    FindMinMax(startIndex, lastIndex, aSrc, ref minIndex, ref maxIndex, ref MinVal, ref MaxVal);
                    newPoint = new GTWavePoint();
                    newPoint.X = maxIndex;
                    newPoint.Y = MaxVal;
                    newPoint.WaveDir = GTWaveDir.WAVE_DOWN;
                    mWaveList.Add(newPoint);
                }
            }
        }

        /**
         * @brief 이 함수는 주어진 범위에서 최소, 최대값과 그에 해당하는 데이터 소스 상의 인덱스를 찾는다.
         * @param aSIndex : 검색 시작 인덱스
         * @param aEIndex : 검색 종료 인덱스
         * @param aSrc : 검색할 데이터 소스
         * @param aMinIndex : 찾은 최소값 인덱스
         * @param aMaxIndex : 찾은 최대값 인덱스
         * @param aMin : 찾은 최소값
         * @param aMax : 찾은 최대값
         * @remark
         */ 
        private void FindMinMax(int aSIndex, int aEIndex, double[] aSrc, ref int aMinIndex, ref int aMaxIndex, ref double aMin, ref double aMax)
        {
            if (aSrc.Length < 0 || aSIndex > aSrc.Length - 1 || aEIndex > aSrc.Length - 1 || aSIndex > aEIndex)
                return;
            if (aSIndex < 0)
                aSIndex = 0;
            if (aEIndex > aSrc.Length - 1)
                aEIndex = aSrc.Length - 1;

            aMin = aSrc[aEIndex];
            aMax = aSrc[aEIndex];
            aMinIndex = aEIndex;
            aMaxIndex = aEIndex;

            for(int i = aEIndex; i >= aSIndex; i--)
            {
                double curVal = aSrc[i];
                // 등호를 집어 넣음으로써 시간상으로 먼저 일어난 값이 들어가게 된다.
                // 등호를 집어 넣지 않으면 뒤에서 부터 조사하므로 시간상 가장 최근에 일어난 값이 먼저 들어가게 된다.
                if (curVal <= aMin)
                {
                    aMin = curVal;
                    aMinIndex = i;
                }
                if (curVal >= aMax)
                {
                    aMax = curVal;
                    aMaxIndex = i;
                }
            }
        }

        /*
         * @brief 이 함수는 파동에 대하여 최고점, 최저점을 표시하고
         * 최고점으로부터, 그리고 최저점으로부터 번호를 붙인다.
         * 이 함수의 목적은 최고점과 최저점에서 일정한 범위안에서만 진입을 하기 위함이다.
         */ 
    }
}
